/**
 * @file   ExecuteMethodResponsePacket.cpp
 * @author Djuro Drljaca (djurodrljaca@gmail.com)
 * @date   2014-04-26
 * @brief  DON packet object: Execute Method Response
 *
 * Copyright 2014  Djuro Drljaca (djurodrljaca@gmail.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <DON/Core/Packet/ExecuteMethodResponsePacket.h>
#include <DON/Core/Packet/PacketDefines.h>
#include <DON/Core/DataType/UInt16.h>
#include <QtCore/QByteArray>
#include <QtCore/QList>

#define MAX_METHOD_RETURN_VALUE_COUNT    (DON_CORE_PACKET_MAX_PAYLOAD_DATA_ITEM_COUNT - 2)

using namespace DON::Core::Packet;
using namespace DON::Core::DataType;

class ExecuteMethodResponsePacket::Private
{
public:
    Private()
        : referenceSequence(0u),
          responseStatus(ResponseStatus_Invalid),
          methodReturnValueList(QList<const AbstractDataType *>())
    { }

    ~Private()
    {
        removeAllMethodReturnValues();
    }

    void removeAllMethodReturnValues();

    quint16 referenceSequence;
    ResponseStatus responseStatus;
    QList<const AbstractDataType *> methodReturnValueList;
};

ExecuteMethodResponsePacket::ExecuteMethodResponsePacket()
    : AbstractPacket(),
      d(new Private())
{
    setPacketType(PacketType_ExecuteMethodResponse);
}

ExecuteMethodResponsePacket::ExecuteMethodResponsePacket(const quint16 source,
                                                         const quint16 destination,
                                                         const quint16 sequence,
                                                         const quint16 referenceSequence,
                                                         const ResponseStatus responseStatus)
    : AbstractPacket(source,
                     destination,
                     PacketType_ExecuteMethodResponse,
                     sequence),
      d(new Private())
{
    d->referenceSequence = referenceSequence;
    d->responseStatus = responseStatus;
}

ExecuteMethodResponsePacket::ExecuteMethodResponsePacket(
        const ExecuteMethodResponsePacket &other)
    : AbstractPacket(other),
      d(new Private())
{
    d->referenceSequence = other.d->referenceSequence;
    d->responseStatus = other.d->responseStatus;

    const int size = other.d->methodReturnValueList.size();

    for (int index = 0; index < size; ++index)
    {
        const AbstractDataType *returnValue = other.d->methodReturnValueList.at(index);

        if (returnValue != NULL)
        {
            d->methodReturnValueList.append(returnValue->clone());
        }
    }
}

ExecuteMethodResponsePacket::~ExecuteMethodResponsePacket()
{
    delete d;
}

ExecuteMethodResponsePacket & ExecuteMethodResponsePacket::operator=(
        const ExecuteMethodResponsePacket &other)
{
    AbstractPacket::operator=(other);

    d->referenceSequence = other.d->referenceSequence;
    d->responseStatus = other.d->responseStatus;

    d->removeAllMethodReturnValues();

    const int size = other.d->methodReturnValueList.size();

    for (int index = 0; index < size; ++index)
    {
        const AbstractDataType *returnValue = other.d->methodReturnValueList.at(index);

        if (returnValue != NULL)
        {
            d->methodReturnValueList.append(returnValue->clone());
        }
    }

    return *this;
}

bool ExecuteMethodResponsePacket::isValid() const
{
    bool valid = false;

    if (isResponseStatusValid(d->responseStatus))
    {
        valid = true;
    }

    if (valid)
    {
        if (d->methodReturnValueList.size() > MAX_METHOD_RETURN_VALUE_COUNT)
        {
            valid = false;
        }
        else
        {
            const int size = d->methodReturnValueList.size();

            for (int index = 0; index < size; ++index)
            {
                const AbstractDataType *returnValue = d->methodReturnValueList.at(index);

                if (returnValue != NULL)
                {
                    valid = isDataTypeValid(returnValue->getDataType());
                }
                else
                {
                    valid = false;
                }

                if (valid == false)
                {
                    break;
                }
            }
        }
    }

    return valid;
}

quint16 ExecuteMethodResponsePacket::getReferenceSequence() const
{
    return d->referenceSequence;
}

void ExecuteMethodResponsePacket::setReferenceSequence(const quint16 referenceSequence)
{
    d->referenceSequence = referenceSequence;
}

ResponseStatus ExecuteMethodResponsePacket::getResponseStatus() const
{
    return d->responseStatus;
}

void ExecuteMethodResponsePacket::setResponseStatus(const ResponseStatus responseStatus)
{
    d->responseStatus = responseStatus;
}

int ExecuteMethodResponsePacket::getMethodReturnValueCount() const
{
    return d->methodReturnValueList.size();
}

const AbstractDataType * ExecuteMethodResponsePacket::getMethodReturnValue(const int index) const
{
    const AbstractDataType *returnValue = NULL;

    if ((index >= 0) &&
        (index < d->methodReturnValueList.size()))
    {
        returnValue = d->methodReturnValueList.at(index);
    }

    return returnValue;
}

bool ExecuteMethodResponsePacket::setMethodReturnValue(const AbstractDataType &returnValue,
                                                       const int index)
{
    bool success = false;

    if ((index >= 0) &&
        (index < d->methodReturnValueList.size()))
    {
        delete d->methodReturnValueList[index];

        d->methodReturnValueList[index] = returnValue.clone();

        if (d->methodReturnValueList.at(index) != NULL)
        {
            success = true;
        }
    }

    return success;
}

bool ExecuteMethodResponsePacket::addMethodReturnValue(const AbstractDataType &returnValue)
{
    bool success = false;

    if (d->methodReturnValueList.size() < MAX_METHOD_RETURN_VALUE_COUNT)
    {
        d->methodReturnValueList.append(returnValue.clone());

        if (d->methodReturnValueList.last() != NULL)
        {
            success = true;
        }
    }

    return success;
}

bool ExecuteMethodResponsePacket::removeMethodReturnValue(const int index) const
{
    bool success = false;

    if ((index >= 0) &&
        (index < d->methodReturnValueList.size()))
    {
        delete d->methodReturnValueList.takeAt(index);
        success = true;
    }

    return success;
}

void ExecuteMethodResponsePacket::removeAllMethodReturnValues()
{
    d->removeAllMethodReturnValues();
}

int ExecuteMethodResponsePacket::getMaxMethodReturnValueCount()
{
    return MAX_METHOD_RETURN_VALUE_COUNT;
}

QList<QByteArray> ExecuteMethodResponsePacket::getPayloadDataItems(bool *ok) const
{
    QList<QByteArray> payloadDataItems;
    bool success = false;

    if (isValid())
    {
        success = true;

        // Reference Sequence
        payloadDataItems.append(UInt16::toBinary(d->referenceSequence));

        // Response Status
        const char responseStatus = fromResponseStatusToBinary(d->responseStatus);
        payloadDataItems.append(QByteArray(1, responseStatus));

        // Return Value List
        const int size = d->methodReturnValueList.size();

        for (int index = 0; index < size; ++index)
        {
            // Return value
            const AbstractDataType *returnValue = d->methodReturnValueList.at(index);
            const char dataTypeData = fromDataTypeToBinary(returnValue->getDataType());
            const QByteArray valueData = returnValue->toBinary();

            if (valueData.isEmpty())
            {
                // Error
                payloadDataItems.clear();
                success = false;
            }
            else
            {
                // Add return value to the list
                QByteArray returnValueData;
                returnValueData.append(dataTypeData);
                returnValueData.append(valueData);

                payloadDataItems.append(returnValueData);
            }
        }
    }

    if (ok != NULL)
    {
        *ok = success;
    }

    return payloadDataItems;
}

void ExecuteMethodResponsePacket::Private::removeAllMethodReturnValues()
{
    const int size = methodReturnValueList.size();

    for (int index = 0; index < size; ++index)
    {
        delete methodReturnValueList.at(index);
    }

    methodReturnValueList.clear();
}
